package com.nulldev.util.JVM;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import com.nulldev.util.JVM.reflections.ClassCache;
import com.nulldev.util.VariableAPI.ArrayUtils;

public class LegacyReflectionUtils {
	public static Class<?> fetchClass(final String fullClassPath) {
		if (JVM.version() > 1.9) { // JRE 9 or newer
			throw new UnsupportedOperationException("Backport. -> Due to JDK changes, this function is blocked in this JRE.");
		}
		try {
			return ClassCache.forName(fullClassPath);
		} catch (Exception e) {
			throw new RuntimeException("Class not found.");
		}
	}

	@SuppressWarnings("rawtypes")
	public static Object createInstance(final String className) {
		try {
			final Constructor cns = ClassCache.forName(className).getConstructor();
			cns.setAccessible(true);
			return cns.newInstance();
		} catch (Exception e) {
			try {
				final Constructor cns = ClassCache.forName(className).getDeclaredConstructor();
				cns.setAccessible(true);
				return cns.newInstance();
			} catch (Exception e3) {
				try {
					return ClassCache.forName(className).newInstance();
				} catch (Exception e2) {
					try {
						return LegacyReflectionUtils.class.getClassLoader().loadClass(className).newInstance();
					} catch (Exception ef) {
						// log.error("Failed to create instance.", ef);
						ef.printStackTrace();
						return new Object();
					}
				}
			}
		}
	}

	/**
	 * Gets a field value
	 * 
	 * @param targetObject [class] (Cannot be Foo.class must be a instance from new
	 *                     Foo() or a field with new Foo())
	 * @param fieldName    Name of field such as itemVal
	 * @param fieldValue   New value to set
	 * @return
	 */
	@SuppressWarnings(
		{ "rawtypes", "unchecked" })
	public static <T> T getField(final Object targetObject, final String fieldName) {
		Field field;
		try {
			field = targetObject.getClass().getDeclaredField(fieldName);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("Failed to find field in class.");
		}
		Class superClass = targetObject.getClass().getSuperclass();
		while (field == null && superClass != null) {
			try {
				field = superClass.getDeclaredField(fieldName);
			} catch (NoSuchFieldException e) {
				superClass = superClass.getSuperclass();
			}
		}
		field.setAccessible(true);
		try {
			return (T) field.get(targetObject);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Failed to get field.");
		}
	}

	/**
	 * Set a field value
	 * 
	 * @param targetObject [class] (Cannot be Foo.class must be a instance from new
	 *                     Foo() or a field with new Foo())
	 * @param fieldName    Name of field such as itemVal
	 * @param fieldValue   New value to set
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static void setField(final Object targetObject, final String fieldName, final Object fieldValue) {
		Field field;
		try {
			field = targetObject.getClass().getDeclaredField(fieldName);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException("Failed to find field in class.");
		}
		Class superClass = targetObject.getClass().getSuperclass();
		while (field == null && superClass != null) {
			try {
				field = superClass.getDeclaredField(fieldName);
			} catch (NoSuchFieldException e) {
				superClass = superClass.getSuperclass();
			}
		}
		field.setAccessible(true);
		try {
			field.set(targetObject, fieldValue);
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Failed to modify field.");
		}
	}

	/**
	 * Call a function
	 * 
	 * @param targetClass (Cannot be Foo.class must be a instance from new Foo() or
	 *                    a field with new Foo())
	 * @param fieldName   (Name of the function)
	 * @param inputs      (Variables in foo.function() - If there are none, leave it
	 *                    as null.)
	 * @return
	 */
	@SuppressWarnings(value =
		{ "all" })
	public static Object invokeFunction(final Object targetClass, final String fieldName, final Object... inputs) {
		Method method;
		Object output = null;
		try {
			method = targetClass.getClass().getDeclaredMethod(fieldName, ArrayUtils.classes(inputs));
		} catch (Exception e) {
			try {
				method = targetClass.getClass().getMethod(fieldName, null);
			} catch (Exception e2) {
//				if (log.debugEnabled) {
//					log.debug("Printing functions...");
//					for (final Method t : targetClass.getClass().getMethods()) {
//						log.debug("\t" + t.getName() + " - func() by " + t.getDeclaringClass().getName());
//					}
//				}
				throw new RuntimeException("Failed to find method '" + fieldName + "'.", e2);
			}
		}
		try {
			if (!method.isAccessible()) {
				method.setAccessible(true);
			}
			output = method.invoke(targetClass, inputs);
		} catch (Exception e) {
			try {
				output = method.invoke(null, inputs);
			} catch (Exception e2) {
				throw new RuntimeException("Failed to invoke method.", e2);
			}
		}
		return output;
	}
}
